package com.gitee.wsl.io.buffer

import com.gitee.wsl.lang.ByteOrder
import kotlin.math.min

@OptIn(ExperimentalUnsignedTypes::class)
class UByteBuffer @OptIn(ExperimentalUnsignedTypes::class) constructor(
    capacity: Int,
    order: ByteOrder = ByteOrder.LittleEndian,
    isReadOnly: Boolean = false,
    override var buf: UByteArray = UByteArray(capacity)
): ByteBufferBase<UByte, UByteArray>(capacity, order, isReadOnly) {

    constructor(bytes: UByteArray, order: ByteOrder = ByteOrder.LittleEndian) :
            this(bytes.size, order, false, bytes)

    /**
     * Similar to [ByteArray] copyInto
     * @param destination Array to receive copy
     * @param destinationOffset index into destination where copy will start
     * @param startIndex index in source array where copy will start
     * @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
     * be number of bytes copied.
     */
    override fun copyInto(
        destination: UByteArray,
        destinationOffset: Int,
        startIndex: Int,
        endIndex: Int
    ) {
        buf.copyInto(destination, destinationOffset, startIndex, endIndex)
    }

    override fun compareElement(element: UByte, other: UByte): Int {
        return element.compareTo(other)
    }

    /**
     * Increase size of buffer. Capacity and content are increased. Position is unchanged. if limit
     * currently set to capacity, it will be set to the new capacity. All data is retained from
     * previous buffer, including bytes between old limit and capacity.
     * @param addCapacity bytes to add. Unsigned as can't be used to shrink.
     */
    override fun expand(addCapacity: UInt) {
        val changeLimit = limit == capacity
        capacity += addCapacity.toInt()
        val newBuf = UByteArray(capacity)
        buf.copyInto(newBuf, 0, 0)
        buf = newBuf
        if (changeLimit) limit = capacity
    }

    /**
     * Appends a buffer starting at its position, to the end of this buffer,
     * starting at position of this buffer for appendBuffer remaining size. New position will
     * be at new limit. If the contents must be expanded (usually is), then a new byteArray is allocated.
     * position is moved to the new limit.
     * @param appendBuffer buffer to be appended, starting at its poition for remaining bytes
     * @return this
     */
    fun expand(appendBuffer: UByteBuffer) {
        val newLimit = position + appendBuffer.remaining
        if (newLimit > capacity) {
            val oldBuf = buf
            buf = UByteArray(newLimit)
            oldBuf.copyInto(buf, 0, 0, position)
        }
        appendBuffer.contentBytes.copyInto(
            buf,
            position,
            appendBuffer.position,
            appendBuffer.remaining
        )
        capacity = newLimit
        limit = newLimit
        position = limit
    }

    override fun flip(): UByteBuffer {
        super.flip()
        return this
    }

    override fun getElementAt(index: Int): UByte {
        return buf[index]
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned Int will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return Int will not have it's high order bits set.
     */
    override fun getElementAsInt(index: Int): Int {
        return buf toPosInt index
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned UInt will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return UInt will not have it's high order bits set.
     */
    override fun getElementAsUInt(index: Int): UInt {
        return buf toPosUInt index
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned Long will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return Long will not have it's high order bits set.
     */
    override fun getElementAsLong(index: Int): Long {
        return buf toPosLong index
    }

    /**
     * gets one byte at the current position without changing the position. The byte is treated as
     * unsigned, so the returned ULong will always be positive.
     * @param index indicates which element in the current array to retrieve
     * @return ULong will not have it's high order bits set.
     */
    override fun getElementAsULong(index: Int): ULong {
        return buf toPosULong index
    }

    fun get(
        destination: UByteArray,
        destinationOffset: Int = 0,
        size: Int = destination.size
    ) {
        super.fillArray(destination, destinationOffset, size)
    }

    override fun getBytes(length: Int): UByteArray {
        val l = min(remaining, length)
        val a = UByteArray(l)
        buf.copyInto(a, 0, position, position + l)
        position += l
        return a
    }

    override fun getBytes(bytes: UByteArray) {
        val l = min(remaining, bytes.size)
        buf.copyInto(bytes, 0, position, position + l)
        position += l
    }

    override fun put(bytes: UByteArray) {
        val l = min(remaining, bytes.size)
        bytes.copyInto(buf, position, 0, l)
        position += l
    }

    /**
     * Copies specified bytes from source to this buffer, starting at position, for the
     * specified length. Position is incremented by the length. Any bounds violation throws
     * an IllegalArgumentException
     *
     * @param source byte array to write from, will be unchanged
     * @param sourceOffset starting offset in source, defaults to 0
     * @param length number of bytes to copy, defaults to size of source
     * @return this
     * @throws IllegalArgumentException on bounds violation
     */
    fun putBytes(source: UByteArray, sourceOffset: Int = 0, length: Int = source.size) {
        checkBounds(sourceOffset, length, length)
        if (length > remaining)
            throw IllegalArgumentException("Length:$length exceeds remaining:$remaining")
        for (i in sourceOffset until sourceOffset + length) {
            byte = source[i]
        }
    }

    override fun putEndian(bytes: UByteArray) {
        if (order == ByteOrder.LittleEndian) {
            bytes.reverse()
        }
        put(bytes)
    }

    override fun setElementAt(index: Int, element: UByte) {
        buf[index] = element
    }

    /**
     * Make a new ByteBuffer containing the [remaining bytes] of this one. Length can be overriden to
     * a shorter value than the default [remaining]. Position is unaffected
     * @param length defaults to [remaining]. can be between 1 and [remaining]
     */
    override fun slice(length: Int): UByteBuffer {
        val bytes = UByteArray(length)
        buf.copyInto(bytes, 0, position, position + length)
        return UByteBuffer(bytes, this.order)
    }

    override fun shortToArray(short: Short): UByteArray {
        return ubyteArrayOf(
            (short.toInt() shr 8).toUByte(),
            short.toUByte()
        )
    }

    override fun ushortToArray(ushort: UShort): UByteArray {
        return ubyteArrayOf(
            (ushort.toUInt() shr 8).toUByte(),
            ushort.toUByte()
        )
    }

    override fun intToArray(int: Int): UByteArray {
        return ubyteArrayOf(
            (int shr 24 and 0xff).toUByte(),
            (int shr 16 and 0xff).toUByte(),
            (int shr 8 and 0xff).toUByte(),
            (int and 0xff).toUByte()
        )
    }

    override fun uintToArray(int: UInt): UByteArray {
        return ubyteArrayOf(
            (int shr 24 and 0xffu).toUByte(),
            (int shr 16 and 0xffu).toUByte(),
            (int shr 8 and 0xffu).toUByte(),
            (int and 0xffu).toUByte()
        )
    }

    override fun longToArray(long: Long): UByteArray {
        return ubyteArrayOf(
            (long shr 56 and 0xff).toUByte(),
            (long shr 48 and 0xff).toUByte(),
            (long shr 40 and 0xff).toUByte(),
            (long shr 32 and 0xff).toUByte(),
            (long shr 24 and 0xff).toUByte(),
            (long shr 16 and 0xff).toUByte(),
            (long shr 8 and 0xff).toUByte(),
            (long and 0xff).toUByte()
        )
    }

    override fun ulongToArray(uLong: ULong): UByteArray {
        return ubyteArrayOf(
            (uLong shr 56 and 0xffu).toUByte(),
            (uLong shr 48 and 0xffu).toUByte(),
            (uLong shr 40 and 0xffu).toUByte(),
            (uLong shr 32 and 0xffu).toUByte(),
            (uLong shr 24 and 0xffu).toUByte(),
            (uLong shr 16 and 0xffu).toUByte(),
            (uLong shr 8 and 0xffu).toUByte(),
            (uLong and 0xffu).toUByte()
        )
    }

    /**
     * Convert from a [UByteBuffer] to a [ByteBuffer], retaining the same capacity, position, limit
     * and contents
     */
    fun toByteBuffer(): ByteBuffer {
        val uBuf = ByteBuffer(capacity, order, isReadOnly, contentBytes.toByteArray())
        uBuf.positionLimit(position, remaining)
        return uBuf
    }

    override fun toString(): String {
        return buildString {
            append("Position: $position, limit: $limit, remaining: $remaining. Content: 0x")
            for (i in position until limit) {
                append("${contentBytes[i].toString(16).padStart(2, '0')} ")
            }
        }
    }

    /**
     * Tells whether or not this buffer is equal to another object.
     *
     * <p> Two byte buffers are equal if, and only if,
     *
     * <ol>
     *
     *   <li><p> They have the same element type,  </p></li>
     *
     *   <li><p> They have the same number of remaining elements, and
     *   </p></li>
     *
     *   <li><p> The two sequences of remaining elements, considered
     *   independently of their starting positions, are pointwise equal.

     *   </p></li>
     *
     * </ol>
     *
     * <p> A byte buffer is not equal to any other type of object.  </p>
     *
     * @param other The object to which this buffer is to be compared
     *
     * @return <tt>true</tt> if, and only if, this buffer is equal to the
     *           given object
     */
    override fun equals(other: Any?): Boolean {
        if (other == null) return false
        if (this === other) return true
        if (other !is UByteBuffer) return false
        if (remaining != other.remaining) return false
        var i = limit - 1
        var j = other.limit - 1
        while (i >= position) {
            if (buf[i--] != other.buf[j--]) return false
        }
        return true
    }

    /**
     * Returns the current hash code of this buffer.
     *
     * <p> The hash code of a byte buffer depends only upon its remaining
     * elements; that is, upon the elements from <tt>position()</tt> up to, and
     * including, the element at <tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>.
     *
     * <p> Because buffer hash codes are content-dependent, it is inadvisable
     * to use buffers as keys in hash maps or similar data structures unless it
     * is known that their contents will not change.  </p>
     *
     * @return The current hash code of this buffer
     */
    override fun hashCode(): Int {
        var h = 1
        val p: Int = position
        for (i in limit - 1 downTo p) h = 31 * h + buf[i].toInt()
        return h
    }
}